package com.moli.iov.handle.alarm.biz.real;

import com.alibaba.fastjson.JSONObject;
import com.moli.iov.base.enums.AlarmAuthEnum;
import com.moli.iov.base.enums.FenceShapeEnum;
import com.moli.iov.exception.BusinessException;
import com.moli.iov.handle.alarm.biz.utils.AlarmUtils;
import com.moli.iov.handle.alarm.constants.Constant;
import com.moli.iov.handle.alarm.enums.AlarmCodeEnum;
import com.moli.iov.handle.alarm.enums.AlarmCodeStatusEnum;
import com.moli.iov.handle.alarm.enums.AlarmSourceEnum;
import com.moli.iov.handle.alarm.enums.AlarmTypeEnum;
import com.moli.iov.handle.alarm.enums.AntiShakeEnum;
import com.moli.iov.handle.alarm.model.dto.AlarmDto;
import com.moli.iov.handle.alarm.model.dto.AuthDto;
import com.moli.iov.handle.alarm.model.dto.DisarmAlarmDto;
import com.moli.iov.handle.alarm.model.dto.FenceDto;
import com.moli.iov.handle.alarm.model.dto.GeoDto;
import com.moli.iov.handle.alarm.model.dto.VehicleFenceDto;
import com.moli.iov.handle.alarm.model.po.extend.FenceInfoExtend;
import com.moli.iov.handle.alarm.service.IAlarmInfoRequestService;
import com.moli.iov.handle.alarm.service.IAlarmOtherService;
import com.moli.iov.protocol.model.realtime.RealTimeInfoReq;
import com.moli.iov.protocol.model.realtime.VehicleGpsReq;
import com.moli.iov.util.CoordTransformUtil;
import com.moli.iov.util.GeoUtils;
import com.moli.iov.util.StringUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * <p>
 * 实时上报围栏报警 进出区域报警
 * </p>
 *
 * @author: zql
 * @Date: 2019/2/19 10:25
 */
@Slf4j
@Component("fenceReal")
public class FenceReal {

  @Autowired
  private IAlarmInfoRequestService alarmInfoRequestService;

  @Autowired
  private IAlarmOtherService alarmOtherService;

  @Autowired
  private AlarmUtils alarmUtils;

  public void handle(RealTimeInfoReq realTimeInfoReq, AuthDto authDto) {
    try {

      if (realTimeInfoReq.vehicleGpsReq == null) {

        log.error("围栏报警 失败，vehicleGpsReq为空 vin:{} ",
            realTimeInfoReq.vin);
        return;
      }

      fence(realTimeInfoReq, authDto);
    } catch (Exception e) {
      log.error("实时平台判断 （进出区域报警、蓄电池低电压报警、SOC电压低报警）异常",
          e);
    }
  }

  /**
   * 围栏报警
   */
  public void fence(RealTimeInfoReq realTimeInfoReq, AuthDto authDto) {

    String vin = realTimeInfoReq.vin;
    VehicleGpsReq vehicleGpsReq = realTimeInfoReq.vehicleGpsReq;
    double speed = realTimeInfoReq.vehicleAllReq == null ? 0 : realTimeInfoReq.vehicleAllReq.getSpeed();

    //TODO  2020.4.21 需要改成报警解除时不做速度校验
    if(speed <= 0){
      return;
    }

    double[] loglats =
        CoordTransformUtil.transformWGS84ToBD09(vehicleGpsReq.longitude, vehicleGpsReq.latitude);
    double lon = loglats[0];
    double lat = loglats[1];

    log.info("围栏报警 坐标转换前：{},{}  转换百度后：{},{}",
        vehicleGpsReq.longitude, vehicleGpsReq.latitude, lon, lat);

    Map<String, List<VehicleFenceDto>> fenceMap = alarmOtherService.listFencesByVin(vin);
    try {
      inOutFenceAlarm(vin, lon, lat,
          AlarmCodeEnum.OUT_FENCE, fenceMap.get(Constant.MAP_KEY_OUT),
          authDto);
    } catch (Exception e) {
      log.error("出栏报警异常", e);
    }

    try {
      inOutFenceAlarm(vin, lon, lat,
          AlarmCodeEnum.IN_FENCE, fenceMap.get(Constant.MAP_KEY_IN),
          authDto);
    } catch (Exception e) {
      log.error("进栏报警异常", e);
    }
  }

  /**
   * 进出栏报警
   */
  public void inOutFenceAlarm(String vin, double lon, double lat,
      AlarmCodeEnum alarmCodeEnum, List<VehicleFenceDto> vehicleFenceDtos, AuthDto authDto) {
    try {

      FenceDto fenceDto = null;
      if (vehicleFenceDtos == null || vehicleFenceDtos.isEmpty()) {
        fenceDto = new FenceDto(AlarmCodeStatusEnum.ALARM_CANCELLED.getFlag(),
            "");
      }else if (AlarmCodeEnum.OUT_FENCE == alarmCodeEnum) {
        fenceDto = isOutFence(vin, lon, lat, vehicleFenceDtos);
      }else{
        fenceDto = isInFence(lon, lat, vehicleFenceDtos);
      }

      fenceAlarm(vin, alarmCodeEnum, AlarmTypeEnum.ALARM.getFlag(), fenceDto, lon, lat, authDto);

    } catch (BusinessException e) {
      log.error("进出栏报警 失败 原因：{}",
          e.getMessage());
    } catch (Exception e) {
      log.error("进出栏报警 异常",
          e);
    }
  }

  /**
   * 围栏报警
   */
  public void fenceAlarm(
      String vin,AlarmCodeEnum alarmCodeEnum,int alarmType,
      FenceDto outFenceDto,double lon,double lat, AuthDto authDto) {

    if (outFenceDto.isAlarm()) {

      if(authDto.getAlarmAuths().contains(AlarmAuthEnum.FENCE.getFlag())){
        log.info("围栏报警 不监控报警 vin:{}", vin);
        return;
      }

      log.info("围栏报警 报警 vin:{} 报警类型：{} longitude：{} latitude：{} 围栏id: {} 围栏名称：{}",
          vin, alarmCodeEnum.getMsg(), lon, lat, outFenceDto.getFenceId(), outFenceDto.getFenceName());

      AlarmDto alarmDto = new AlarmDto(vin, alarmCodeEnum, alarmType,AlarmSourceEnum.PLATFORM.getFlag(),
          outFenceDto.getMemo(), AntiShakeEnum.YES.getFlag(), lon, lat);

      alarmDto.setFenceId(outFenceDto.getFenceId());
      alarmDto.setFenceName(outFenceDto.getFenceName());

      alarmInfoRequestService.alarm(alarmDto);
      return;
    }

    alarmInfoRequestService.disarmAlarm(new DisarmAlarmDto(vin,
        alarmCodeEnum.getFlag(),AntiShakeEnum.YES.getFlag(),lon,lat));
  }

  /**
   * 进栏报警判断
   */
  public FenceDto isInFence(double longitude, double latitude,
      List<VehicleFenceDto> vehicleFenceDtos) {
    FenceDto fenceDto = new FenceDto();
    for (int i = 0; i < vehicleFenceDtos.size(); i++) {
      VehicleFenceDto vehicleFenceDto = vehicleFenceDtos.get(i);
      try {

        boolean isPointInPolygon = isInOutFenceReal(vehicleFenceDto, longitude, latitude);
        if (isPointInPolygon) {
          fenceDto.setFenceId(vehicleFenceDto.getId());
          fenceDto.setFenceName(vehicleFenceDto.getFenceName());
          break;
        }
      } catch (Exception e) {
        log.error("进栏报警判断 异常", e);
      }
    }

    fenceDto.setAlarm(AlarmCodeStatusEnum.ALARM_CANCELLED.getFlag());
    fenceDto.setMemo("");
    if (fenceDto.getFenceId() == null) {
      return fenceDto;
    }

    fenceDto.setAlarm(AlarmCodeStatusEnum.ALARMING.getFlag());
    fenceDto.setMemo(Constant.IN_FENCE + fenceDto.getFenceName());
    return fenceDto;

  }

  /**
   * 获取出围栏报警时，车辆所处的围栏
   */
  public FenceInfoExtend getFenceInfoExtend(String vin,double longitude, double latitude, List<VehicleFenceDto> vehicleFenceDtos) {
    String fenceStr = alarmUtils.get(vin);
    if (StringUtil.isNotBlank(fenceStr)) {
      return JSONObject.parseObject(fenceStr, FenceInfoExtend.class);
    }

    FenceInfoExtend fenceInfoExtend = new FenceInfoExtend();
    double minDistance = 0;
    for(VehicleFenceDto vf: vehicleFenceDtos){
      GeoDto geoDto = JSONObject.parseObject(vf.getFenceAreaCoordinate(), GeoDto.class);
      double distance = GeoUtils
          .getDistance(latitude, longitude, geoDto.getLatitude(), geoDto.getLongitude());

      if(minDistance == 0 || distance < minDistance){
        fenceInfoExtend.setFenceId(vf.getId());
        fenceInfoExtend.setFenceName(vf.getFenceName());
      }

      log.info("获取出围栏报警时，车辆所处的围栏 distance:{} fenceId:{}, fenceName:{}",
          distance, vf.getId(), vf.getFenceName());
      minDistance = distance;
    }

    return fenceInfoExtend;
  }

  /**
   * 出栏报警判断
   */
  public FenceDto isOutFence(String vin, double longitude, double latitude, List<VehicleFenceDto> vehicleFenceDtos) {

    //车辆当前处的围栏
    FenceInfoExtend fenceInfoExtend = getFenceInfoExtend(vin, longitude, latitude,
        vehicleFenceDtos);
    List<Boolean> alarms = new ArrayList<>();
    for (int i = 0; i < vehicleFenceDtos.size(); i++) {

      VehicleFenceDto vehicleFenceDto = vehicleFenceDtos.get(i);
      try {

        boolean isPointInPolygon = isInOutFenceReal(vehicleFenceDto, longitude, latitude);
        if (!isPointInPolygon) {
          alarms.add(true);
        }else{
          //设置车辆所处的围栏
          alarmUtils.setVehicleFenceIfNon(vin, new FenceInfoExtend(vehicleFenceDto.getId(), vehicleFenceDto.getFenceName()));
        }
      } catch (Exception e) {
        log.error("出栏报警判断 异常", e);
      }
    }

    boolean isAlarming = !alarms.isEmpty() && alarms.size() >= vehicleFenceDtos.size();

    FenceDto fenceDto = new FenceDto(AlarmCodeStatusEnum.ALARM_CANCELLED.getFlag(), "");
    if (!isAlarming) {
      return fenceDto;
    }

    fenceDto.setAlarm(AlarmCodeStatusEnum.ALARMING.getFlag());
    if(fenceInfoExtend != null){
      fenceDto.setFenceId(fenceInfoExtend.getFenceId());
      fenceDto.setFenceName(fenceInfoExtend.getFenceName());
      fenceDto.setMemo(Constant.OUT_FENCE + fenceInfoExtend.getFenceName());
    }

    return fenceDto;
  }

  /**
   * 进出栏报警判断
   *
   * @param vehicleFenceDto 围栏DTO
   */
  public boolean isInOutFenceReal(VehicleFenceDto vehicleFenceDto, double longitude, double latitude) {

    if (FenceShapeEnum.CIRCLE.getCode() == vehicleFenceDto.getFenceShape()) {
      return circle(vehicleFenceDto, longitude, latitude);
    }

    return polygon(vehicleFenceDto, longitude, latitude);
  }

  /**
   * 圆 是否在圆内
   */
  public boolean circle(VehicleFenceDto vehicleFenceDto, double longitude, double latitude) {

    String fenceAreaCoordinate = vehicleFenceDto.getFenceAreaCoordinate();
    GeoDto geoDto = JSONObject.parseObject(fenceAreaCoordinate, GeoDto.class);
    //米转换成千米
    Double radius = vehicleFenceDto.getRadius()/Constant.RADIUS_UNIT;
    return GeoUtils.isInCircle(radius, geoDto.getLatitude(), geoDto.getLongitude(), latitude, longitude);
  }

  /**
   * 多边形判断
   */
  public boolean polygon(VehicleFenceDto vehicleFenceDto, double longitude, double latitude) {

    List<Map<String, Double>> maps = convertPolygonList(
        vehicleFenceDto.getFenceAreaCoordinateRange());
    return GeoUtils.isPointInPolygon(maps, longitude, latitude);
  }

  /**
   * fenceArea 转 List<Map<String,Double>>
   */
  public List<Map<String, Double>> convertPolygonList(String fenceAreaCoordinateRange) {

    List<GeoDto> geoDtos = JSONObject.parseArray(fenceAreaCoordinateRange, GeoDto.class);
    List<Map<String, Double>> polygonList = new ArrayList<>();

    geoDtos.forEach(geoDto -> {

      Map<String, Double> map =
          (Map<String, Double>) JSONObject.parse(JSONObject.toJSONString(geoDto));
      polygonList.add(map);

    });

    return polygonList;
  }
}
